using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace SpellCore
{
    public class HunSpellWrapper : ISpellCheckEngine, IDisposable
    {

        #region ICorrectnessProvider Members

        public CheckingResult GetWordStatus(string word)
        {
            if (IsWordSpelledCorrect(word) || IsWordSpelledCorrect(word.Normalize(NormalizationForm.FormC)) ||
                                                        IsWordSpelledCorrect(word.Normalize(NormalizationForm.FormD)))
            {
                return CheckingResult.Good;
            }
            return CheckingResult.Unknown;
        }

        public bool CanSupportAdditions()
        {
            return false;
        }

        public void AddWordAsGood(string word)
        {
            throw new NotSupportedException("cannot do an add good word operation on a HunSpellWrapper class");
        }

        public bool CanSupportBadWords()
        {
            return false;
        }

        #region ISpellCheckEngine Members

        /// <summary>
        /// Adds a word to the known bad word store
        /// </summary>
        /// <param name="word"></param>
        /// <exception cref="NotSupportedException">When CanSupportBadWords is false</exception>
        public void AddWordAsBad(string word)
        {
            throw new NotImplementedException();
        }

        #endregion

        #endregion

        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string lpFileName);

        internal static IntPtr _library;

        private IntPtr _hunspellSession;

        [DllImport("hunspelldll.dll")]
        private static extern IntPtr hunspell_initialize(IntPtr aff_file,
                                                         IntPtr dict_file);
        
        public static HunSpellWrapper CreateWrapper(string pathToEngineRootDirectory, string baseDictionaryWritingSystemPath, string writingSystemId)
        {
            if (string.IsNullOrEmpty(pathToEngineRootDirectory))
            {
                throw new ArgumentException("File path should not be null or empty.", "pathToEngineRootDirectory");
            }
            if (string.IsNullOrEmpty(baseDictionaryWritingSystemPath))
            {
                throw new ArgumentException("File path should not be null or empty.", "baseDictionaryWritingSystemPath");
            }
            if (string.IsNullOrEmpty(writingSystemId))
            {
                throw new ArgumentException("Writing System Id should not be null or empty.", "writingSystemId");
            }
            
            //linux doesn't allow dash, so hunspell uses underline in its place
            writingSystemId = writingSystemId.Replace('-', '_');

            string hunSpellProviderPath = Path.Combine(baseDictionaryWritingSystemPath, "HunSpell");
            if (!Directory.Exists(hunSpellProviderPath))
            {
                return null;
            }

            string dictionaryFile = Path.Combine(hunSpellProviderPath, writingSystemId + ".dic");
            if (!File.Exists(dictionaryFile))
            {
                return null;
            }

            string affixFile = Path.Combine(hunSpellProviderPath, writingSystemId + ".aff");
            if (!File.Exists(affixFile))
            {
                return null;
            }

            if (_library == IntPtr.Zero)
            {
                _library = LoadLibrary(Path.Combine(pathToEngineRootDirectory, "hunspelldll.dll"));
            }
            if (_library == IntPtr.Zero)
            {
                return null;
            }
            try
            {
                return new HunSpellWrapper(affixFile, dictionaryFile);
            }
            catch
            {
                return null;
            }
        }

        private HunSpellWrapper(string affixFilePath, string dictionaryFilePath)
        {
            using (Utf8Marshaller affixFilePathMarshaller = new Utf8Marshaller(affixFilePath))
            {
                using (Utf8Marshaller dictionaryFilePathMarshaller = new Utf8Marshaller(dictionaryFilePath))
                {
                    _hunspellSession = hunspell_initialize(affixFilePathMarshaller.MarshalledValue, 
                                                           dictionaryFilePathMarshaller.MarshalledValue);
                    if ( _hunspellSession == IntPtr.Zero)//review: what would a failed session give us?
                    {
                        throw new ApplicationException("Couldn't create hunspell session.");
                    }

                }
            }
        }

        //frees spell object
        [DllImport("hunspelldll.dll")]
        private static extern void hunspell_uninitialize(IntPtr hunspellSession);
        
        ~HunSpellWrapper()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool _isDisposed;
        private void Dispose(bool disposing)
        {
            if(_isDisposed){
                return;
            }
            _isDisposed = true;
            if (disposing)
            {
                // release managed resources
            }
            // release unmanaged resources
            hunspell_uninitialize(_hunspellSession);
        }

        //spellcheck word, returns 1 if word ok otherwise 0
        [DllImport("hunspelldll.dll")]
        private static extern int hunspell_spell(IntPtr hunspellSession, IntPtr word);

        private bool IsWordSpelledCorrect(string word)
        {
            bool result;
            using (Utf8Marshaller utf8Word = new Utf8Marshaller(word))
            {
                result = (hunspell_spell(_hunspellSession, utf8Word.MarshalledValue) == 1);
            }
            return result;
        }

        #region Suggestion Members
        public bool CanProvideSuggestions()
        {
            return true;
        }


        public IEnumerable<SimilarityCheckResult> GetSuggestions(string word)
        {
            IntPtr addressOfSuggestionList = Marshal.AllocHGlobal(IntPtr.Size);
            int suggestionCount = hunspell_suggest(_hunspellSession, word, addressOfSuggestionList);
            IntPtr suggestionList = Marshal.ReadIntPtr(addressOfSuggestionList);
            Marshal.FreeHGlobal(addressOfSuggestionList);

            List<string> suggestions = Utf8Marshaller.MarshalFromUtf8Array(suggestionList, suggestionCount);
            hunspell_suggest_free(_hunspellSession, suggestionList, suggestionCount);

            List<SimilarityCheckResult> result = new List<SimilarityCheckResult>();
            foreach (string suggestion in suggestions)
            {
                result.Add(new SimilarityCheckResult(SimilarityCheckResult.UnknownScore, suggestion));
            }
            return result;
        }

        ////suggest words for word, returns number of words in slst
        //// YOU NEED TO CALL hunspell_suggest_free after you've done with words
        [DllImport("hunspelldll.dll")]
        private static extern int hunspell_suggest(IntPtr hunspellSession,[MarshalAs(UnmanagedType.LPStr)] string word, IntPtr slst);
        //[DllImport("hunspelldll.dll")]
        //private static extern int hunspell_suggest_auto(IntPtr hunspellSession, [MarshalAs(UnmanagedType.LPStr)] string word, char*** slst);
        ////free slst array
        [DllImport("hunspelldll.dll")]
        private static extern void hunspell_suggest_free(IntPtr hunspellSession, IntPtr slst, int len);
        ////make local copy of returned string!!

        #endregion

        
        //[DllImport("hunspelldll.dll")]
        //private static extern IntPtr hunspell_get_dic_encoding(IntPtr hunspellSession);
        ////add word to dict (word is valid until spell object is not destroyed)
        //[DllImport("hunspelldll.dll")]
        //private static extern int hunspell_put_word(IntPtr hunspellSession, [MarshalAs(UnmanagedType.LPStr)] string word);


        
    }
}
